/*!

 * glDatePicker v2.0

 * http://glad.github.com/glDatePicker/

 *

 * Copyright (c) 2013 Gautam Lad.  All rights reserved.

 * Released under the MIT license.

 *

 * Date: Tue Jan 1 2013

 */

 ;(function() {

	$.fn.glDatePicker = function(options) {

		var pluginName = 'glDatePicker';



		// Find the plugin attached to the element

		var instance = this.data(pluginName);



		// If the instance wasn't found, create it...

		if(!instance) {

			// Return the element being bound to

			return this.each(function() {

				return $(this).data(pluginName, new glDatePicker(this, options));

			});

		}



		// ...otherwise if the user passes true to the plugin (on the second call),

		// then return the instance of the plugin itself

		return (options === true) ? instance : this;

	};



	// Default options

	$.fn.glDatePicker.defaults =

	{

		// Style to use for the calendar.  This name must match the name used in

		// the stylesheet, using the class naming convention "gldp-cssName".

		cssName: 'default',



		// The z-index for the calendar control.

		zIndex: 1000,



		// Thickness of border (in pixels)

		borderSize: 1,



		// The number of pixels to offset the calendar's position on the page.

		calendarOffset: { x: 0, y: 1 },



		// Set to true if you want the calendar to be visible at all times.

		// NOTE: If your target element is hidden, the calendar will be hidden as well.

		showAlways: false,



		// Hide the calendar when a date is selected (only if showAlways is set to false).

		hideOnClick: true,



		// Allow selection of months by clicking on the month in the title.

		allowMonthSelect: true,



		// Allow selection of years by clicking on the year in the title.

		allowYearSelect: true,



		// The date that will be treated as 'today'.

		todayDate: new Date(),



		// The date that will appear selected when the calendar renders.

		// By default it will be set to todayDate.

		selectedDate: null,



		// Arrows used for the Previous and Next month buttons on the title.

		// Set these to blank to hide the arrows completely.

		prevArrow: '\u25c4',

		nextArrow: '\u25ba',



		// A collection of dates that can be selectable by the user.

		// The dates can be a one-time selection or made repeatable by setting

		// the repeatYear or repeatMonth flag to true.

		// By default repeatYear and repeatMonth are false.

		//

		// This example creates 4-individual dates that can be selected;

		// The first date will repeat every year, the second date will repeat every

		// month and year, the third date will repeat every month and the fourth date

		// will only be selectable one-time and not repeat:

		//

		//    selectableDates: [

		//        { date: new Date(0, 8, 5), repeatYear: true },

		//        { date: new Date(0, 0, 14), repeatMonth: true, repeatYear: true },

		//        { date: new Date(2013, 0, 24), repeatMonth: true },

		//        { date: new Date(2013, 11, 25) },

		//    ]

		selectableDates: null,



		// A collection of date ranges that are selectable by the user.

		// The ranges can be made to repeat by setting repeatYear to true

		// (repeatMonth is not supported).

		//

		// This example will create 3-sets of selectable date ranges with

		// specific from and to ranges.  The 4th and 5th ranges don't specify

		// the "to" date in which case the "to" date will be the maximum days for

		// the month specified in "from".  The 4th and 5th ranges also repeat every year:

		//

		//     selectableDateRange: [

		//         { from: new Date(2013, 1, 1), to: newDate (2013, 2, 1) },

		//         { from: new Date(2013, 4, 1), to: newDate (2013, 8, 1) },

		//         { from: new Date(2013, 7, 10), to: newDate (2013, 9, 10) },

		//         { from: new Date(0, 8, 10), repeatYear: true }

		//         { from: new Date(0, 9, 1), repeatYear: true }

		//     ]

		selectableDateRange: null,



		// Mark certain dates as special dates.  Similar to selectableDates, this

		// property supports both repeatYear and repeatMonth flags.

		// Each special date can be styled using custom style names and can have

		// data attached to it that will be returned in the onClick callback.

		// The data field can be any custom (JSON style) object.

		//

		// This example creates two (repeatable by year) dates with special data in them.

		// The first date also assigns a special class (which you will have to define).

		//    specialDates: [

		//        {

		//            date: new Date(0, 8, 5),

		//            data: { message: 'Happy Birthday!' },

		//            repeatYear: true,

		//            cssClass: 'special-bday'

		//        },

		//        {

		//            date: new Date(2013, 0, 8),

		//            data: { message: 'Meeting every day 8 of the month' },

		//            repeatMonth: true

		//        }

		//    ]

		specialDates: null,



		// List of months that can be selectable, including when the user clicks

		// on the title to select from the dropdown.

		// This example only makes two months visible; September and December:

		//    selectableMonths: [8, 11]

		selectableMonths : null,



		// List of selectable years.  If not provided, will default to 5-years

		// back and forward.

		// This example only allows selection of dates that have year 2012, 2013, 2015

		//    selectableYears: [2012, 2013, 2015]

		selectableYears: null,



		// List of selectable days of the week.  0 is Sunday, 1 is Monday, and so on.

		// This example allows only Sunday, Tuesday, Thursday:

		//    selectableDOW: [0, 2, 4]

		selectableDOW : null,



		// Names of the month that will be shown in the title.

		// Will default to long-form names:

		//     January, February, March, April, May, June, July,

		//     August, September, October, November, December

		monthNames: null,



		// Names of the days of the Week that will be shown below the title.

		// Will default to short-form names:

		//     Sun, Mon, Tue, Wed, Thu, Fri, Sat

		dowNames: null,



		// The day of the week to start the calendar on.  0 is Sunday, 1 is Monday and so on.

		dowOffset: 0,



		// Callback that will trigger when the user clicks a selectable date.

		// Parameters that are passed to the callback:

		//     el : The input element the date picker is bound to

		//   cell : The cell on the calendar that triggered this event

		//   date : The date associated with the cell

		//   data : Special data associated with the cell (if available, otherwise, null)

		onClick: (function(el, cell, date, data) {

			el.val(date.toLocaleDateString());

		}),



		// Callback that will trigger when the user hovers over a selectable date.

		// This callback receives the same set of parameters as onClick.

		onHover: function(el, cell, date, data) {},



		// Callback that will trigger when the calendar needs to show.

		// You can use this callback to animate the opening of the calendar.

		onShow: function(calendar) { calendar.show(); },



		// Callback that will trigger when the calendar needs to hide.

		// You can use this callback to animate the hiding of the calendar.

		onHide: function(calendar) { calendar.hide(); },



		// First date of the month.

		firstDate: null

	};



	// Our plugin object

	var glDatePicker = (function() {

		// Main entry point.  Initialize the plugin

		function glDatePicker(element, userOptions) {

			// Grab handle to this

			var self = this;



			// Save bound element to el

			self.el = $(element);

			var el = self.el;



			// Merge user options into default options

			self.options = $.extend(true, {}, $.fn.glDatePicker.defaults, userOptions);

			var options = self.options;



			// Find the calendar element if the user provided one

			self.calendar = $($.find('[gldp-el=' + el.attr('gldp-id') + ' ]'));



			// Default first date to selected

			options.selectedDate = options.selectedDate || options.todayDate;

			options.firstDate = (new Date((options.firstDate || options.selectedDate)))._first();



			if(!(el.attr('gldp-id') || '').length) {

				el.attr('gldp-id', 'gldp-' + Math.round(Math.random() * 1e10))

			}



			// Show the plugin on focus

			el

				.addClass('gldp-el')

				.bind('click', function(e) { self.show(e); })

				.bind('focus', function(e) { self.show(e); });



			// If the user is defining the container and it exists, hide it on initial creation.

			// The update function will handle showing if it's showAlways = true

			if(self.calendar.length && !options.showAlways) {

				self.calendar.hide();

			}



			// Hide the plugin on mouse up outside of the plugin

			$(document).bind('mouseup', function(e) {

				var target = e.target;

				var calendar = self.calendar;



				if(!el.is(target) && !calendar.is(target) && calendar.has(target).length === 0 && calendar.is(':visible')) {

					self.hide();

				}

			});



			// Render calendar

			self.render();

		};



		// Public methods

		glDatePicker.prototype =

		{

			show: function() {

				// Hide others and show this calendar

				$.each($('.gldp-el').not(this.el), function(i, o) {

					if(o.length) { o.options.onHide(o.calendar) ; }

				});



				// Show this calendar

				this.options.onShow(this.calendar);

			},



			hide: function() {

				if(this.options && !this.options.showAlways) {

					this.options.onHide(this.calendar);

				}

			},



			// Render the calendar

			render: function(renderCalback) {

				var self = this;

				var el = self.el;

				var options = self.options;

				var calendar = self.calendar;



				// Build a core class (with border) that every element would have

				var coreClass = ' core border ';

				var cssName = 'gldp-' + options.cssName;



				// Get today

				var todayVal = options.todayDate._val();

				var todayTime = todayVal.time;



				// Constants

				var maxRow = 6;

				var maxCol = 7;

				var borderSize = options.borderSize + 'px';



				// Helper function to build selectable list

				var getSelectableList = function(min, max, userList) {

					// Build a default list using min/max

					var resultList = [];

					for(var i = min; i <= max; i++) { resultList.push(i); }



					// If user provided a collection, sanitize list by ensuring it's within range and unique

					if(userList) {

						var newList = [];

						$.each(userList, function(i, v) {

							if(v >= min && v <= max && newList._indexOf(v) < 0) {

								newList.push(v);

							}

						});



						resultList = newList.length ? newList : resultList;

					};



					// Sort the values before returning it

					resultList.sort();



					return resultList;

				};



				// Selectable (constants)

				var selectableMonths = getSelectableList(0, 11, options.selectableMonths);

				var selectableYears = getSelectableList(todayVal.year - 5, todayVal.year + 5, options.selectableYears);

				var selectableDOW = getSelectableList(0, 6, options.selectableDOW);

				var dowNames = options.dowNames || [ '日', '一', '二', '三', '四', '五', '六' ];

				var monthNames = options.monthNames || [ '一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月' ];



				// Create cell width based on el size

				var containerWidth = el.outerWidth();

				var containerHeight = containerWidth;



				// Create cell size based on container size

				var getCellSize = function(_size, _count) {

					return (_size / _count) + ((options.borderSize / _count) * (_count - 1));

				};

				var cellWidth = getCellSize(containerWidth, maxCol);

				var cellHeight = getCellSize(containerHeight, maxRow + 2);



				// If calendar doesn't exist, create it and re-assign it to self

				if(!calendar.length) {

					self.calendar = calendar = $('<div/>')

						.attr('gldp-el', el.attr('gldp-id'))

						.data('is', true)

						.css(

						{

							display: (options.showAlways ? undefined : 'none'),

							zIndex: options.zIndex,

							width: (cellWidth * maxCol) + 'px'

						});



					$('body').append(calendar);

				}

				else {

					if(!eval(calendar.data('is'))) {

						containerWidth = calendar.outerWidth();

						containerHeight = calendar.outerHeight();



						cellWidth = getCellSize(containerWidth, maxCol);

						cellHeight = getCellSize(containerHeight, maxRow + 2);

					}

				}



				// Hide calendar if the target element isn't visible

				if(!el.is(':visible')) { calendar.hide(); }



				// Add core classes and remove calendar's children

				calendar

					.removeClass()

					.addClass(cssName)

					.children().remove();



				// Bind to resize event to position calendar

				var onResize = function() {

					var elPos = el.offset();

					calendar.css(

					{

						top: (elPos.top + el.outerHeight() + options.calendarOffset.y) + 'px',

						left: (elPos.left + options.calendarOffset.x) + 'px'

					});

				};

				$(window).resize(onResize);

				onResize();



				// Create variables for cells

				var cellCSS =

				{

					width: cellWidth + 'px',

					height: cellHeight + 'px',

					lineHeight: cellHeight + 'px'

				};



				// Helper function to setDate

				var setFirstDate = function(_date) {

					if(_date) {

						// Get first date

						options.firstDate = _date;



						// Update the calendar

						self.render();

					}

				};



				var getFirstDate = function(_offset) {

					// Create start date as the first date of the month

					var _date = new Date(options.firstDate);



					// Default to no offset

					_offset = _offset || 0;



					// Find out which months are selectable

					while(true) {

						// Adjust date for month offset

						_date.setMonth(_date.getMonth() + _offset);

						_date.setDate(Math.min(1, _date._max()));



						// If not an offset, break out of the loop

						if(_offset == 0) { break; }



						// Get _date's value

						var dateVal = _date._val();



						// Get local vars

						var dateMonth = dateVal.month;

						var dateYear = dateVal.year;



						// Find the month first

						if(selectableMonths._indexOf(dateMonth) != -1) {

							// If year is in our collection, break...

							if(selectableYears._indexOf(dateYear) != -1) {

								break;

							}

							else {

								// ...otherwise, if it's out of bounds, exit loop

								if(dateYear < selectableYears[0] || dateYear > selectableYears[selectableYears.length - 1]) {

									return null;

								}

							}

						}

					}



					return _date;

				};



				// Get the previous, next first dates

				var prevFirstDate = getFirstDate(-1);

				var nextFirstDate = getFirstDate(1);



				// Get the first date for the current month being rendered

				var firstDate = (options.firstDate = getFirstDate());

				var firstDateVal = firstDate._val();

				var firstDateMonth = firstDateVal.month;

				var firstDateYear = firstDateVal.year;



				// Get the start date in the calendar

				var startDate = new Date(firstDate);



				// Sanitize days of the week offset

				var dowOffset = Math.abs(Math.min(6, Math.max(0, options.dowOffset)));



				// Offset weekdays

				var startOffset = startDate.getDay() - dowOffset;

					startOffset = startOffset < 1 ? -7 - startOffset : -startOffset;

					dowNames = (dowNames.concat(dowNames))

								.slice(dowOffset, dowOffset + 7);



				// Offset the start date

				startDate._add(startOffset);



				// Gather flags for prev/next arrows

				var showPrev = (prevFirstDate);

				var showNext = (nextFirstDate);



				// Create the arrows and title

				var monyearClass = coreClass + 'monyear ';



				var prevCell = $('<div/>')

								.addClass(monyearClass)

								.css(

									$.extend({}, cellCSS,

									{

										borderWidth: borderSize + ' 0 0 ' + borderSize

									})

								)

								.append(

									$('<a/>')

										.addClass('prev-arrow' + (showPrev ? '' : '-off'))

										.html(options.prevArrow)

								)

								.mousedown(function() { return false; })

								.click(function(e) {

									if(options.prevArrow != '' && showPrev) {

										e.stopPropagation();

										setFirstDate(prevFirstDate);

									}

								});



				var titleCellCount = maxCol - 2;

				var titleWidth = (cellWidth * titleCellCount) - (titleCellCount * options.borderSize) + (options.borderSize);

				var titleCell = $('<div/>')

								.addClass(monyearClass + 'title')

								.css(

									$.extend({}, cellCSS,

									{

										width: titleWidth + 'px',

										borderTopWidth: borderSize,

										marginLeft: '-' + (borderSize)

									})

								);



				var nextCell = $('<div/>')

								.addClass(monyearClass)

								.css(

									$.extend({}, cellCSS,

									{

										marginLeft: '-' + (borderSize),

										borderWidth: borderSize + ' ' + borderSize + ' 0 0'

									})

								)

								.append(

									$('<a/>')

										.addClass('next-arrow' + (showNext ? '' : '-off'))

										.html(options.nextArrow)

								)

								.mousedown(function() { return false; })

								.click(function(e) {

									if(options.nextArrow != '' && showNext) {

										e.stopPropagation();

										setFirstDate(nextFirstDate);

									}

								});



				// Add cells for prev/title/next

				calendar

					.append(prevCell)

					.append(titleCell)

					.append(nextCell);



				// Add all the cells to the calendar

				for(var row = 0, cellIndex = 0; row < maxRow + 1; row++) {

					for(var col = 0; col < maxCol; col++, cellIndex++) {

						var cellDate = new Date(startDate);

						var cellClass = 'day';

						var cellZIndex = options.zIndex + (cellIndex);

						var cell = $('<div/>')



						if(!row) {

							cellClass = 'dow';

							cell.html(dowNames[col]);

							cellDate = null;

						}

						else {

							// Get the new date for this cell

							cellDate._add(col + ((row - 1) * maxCol));



							// Get value for this date

							var cellDateVal = cellDate._val();

							var cellDateTime = cellDateVal.time;



							// Variable to hold special data

							var specialData = null;



							// Determine if this date is selectable

							var isSelectable = true;



							// Helper function to get repeat friendly date against current date

							var getRepeatDate = function(v, date) {

								// If repeating, set the date's year and month accordingly

								if(v.repeatYear === true) { date.setYear(cellDateVal.year); }

								if(v.repeatMonth === true) { date.setMonth(cellDateVal.month); }



								return date._val();

							};



							// Assign date for the cell

							cell.html(cellDateVal.date);



							// If we have selectable date ranges

							if(options.selectableDateRange) {

								isSelectable = false;

								$.each(options.selectableDateRange, function(i, v) {

									var dateFrom = v.from;

									var dateTo = (v.to || null);



									// If to is not specified, default to max days in the from month

									dateTo = dateTo || new Date(v.from.getFullYear(), v.from.getMonth(), v.from._max());



									// If repeating year, set the from and two to the current date's year

									dateFrom = getRepeatDate(v, dateFrom);

									dateTo = getRepeatDate(v, dateTo);



									// Test to see if this date is selectable

									if(cellDateTime >= dateFrom.time && cellDateTime <= dateTo.time) {

										isSelectable = true;

										return true;

									}

								});

							}



							// Handle date ranges and collections

							if(options.selectableDates) {

								if((options.selectableDateRange && !isSelectable) || (isSelectable && !options.selectableDateRange)) {

									isSelectable = false;

								}

								$.each(options.selectableDates, function(i, v) {

									var vDate = getRepeatDate(v, v.date);



									if(vDate.time == cellDateTime) {

										return (isSelectable = true);

									}

								});

							}



							// If not active or if not within selectableMonths, set to noday otherwise evaluate accordingly

							if(!isSelectable ||

								selectableYears._indexOf(cellDateVal.year) < 0 ||

								selectableMonths._indexOf(cellDateVal.month) < 0 ||

								selectableDOW._indexOf(cellDateVal.day) < 0) {

								cellClass = 'noday';

							}

							else {

								// Handle active dates and weekends

								cellClass = ([ 'sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat' ])[cellDateVal.day];



								// Handle today or selected dates

								if(firstDateMonth != cellDateVal.month) { cellClass += ' outday'; }

								if(todayTime == cellDateTime) { cellClass = 'today'; cellZIndex += 50; }

								if(options.selectedDate._time() == cellDateTime) { cellClass = 'selected'; cellZIndex += 51; }



								// Handle special dates

								if(options.specialDates) {

									$.each(options.specialDates, function(i, v) {

										var vDate = getRepeatDate(v, v.date);



										if(vDate.time == cellDateTime) {

											cellClass = (v.cssClass || 'special');

											cellZIndex += 52;

											specialData = v.data;

										}

									});

								}



								cell

									.mousedown(function() { return false; })

									.hover(function(e) {

										e.stopPropagation();



										// Get the data from this cell

										var hoverData = $(this).data('data');



										// Call callback

										options.onHover(el, cell, hoverData.date, hoverData.data);

									})

									.click(function(e) {

										e.stopPropagation();



										// Get the data from this cell

										var clickedData = $(this).data('data');



										// Save date to selected and first

										options.selectedDate = options.firstDate = clickedData.date;



										// Update calendar (and auto-hide if necessary)

										self.render(function() {

											if(!options.showAlways && options.hideOnClick) {

												self.hide();

											}

										});



										// Call callback

										options.onClick(el, $(this), clickedData.date, clickedData.data);

									});

							}

						}



						// Update the css for the cell

						$.extend(cellCSS,

						{

							borderTopWidth: borderSize,

							borderBottomWidth: borderSize,

							borderLeftWidth: (row > 0 || (!row && !col)) ? borderSize : 0,

							borderRightWidth: (row > 0 || (!row && col == 6)) ? borderSize : 0,

							marginLeft: (col > 0) ? '-' + (borderSize) : 0,

							marginTop: (row > 0) ? '-' + (borderSize) : 0,

							zIndex: cellZIndex

						});



						// Assign other properties to the cell

						cell

							.data('data', { date: cellDate, data: specialData})

							.addClass(coreClass + cellClass)

							.css(cellCSS);



						// Add cell to calendar

						calendar.append(cell);

					}

				}



				// Render the month / year title



				// Helper function for toggling select and text

				var toggleYearMonthSelect = function(showYear) {

					var show = 'inline-block';

					var hide = 'none';



					if(options.allowMonthSelect) {

						monthText.css({ display: !showYear ? hide : show });

						monthSelect.css({ display: !showYear ? show : hide });

					}



					if(options.allowYearSelect) {

						yearText.css({ display: showYear ? hide : show });

						yearSelect.css({ display: showYear ? show : hide });

					}

				};



				// Helper function when select is updated

				var onYearMonthSelect = function() {

					options.firstDate = new Date(yearSelect.val(), monthSelect.val(), 1);

					self.render();

				};



				// Build month selector

				var monthSelect = $('<select/>')

									.hide()

									.change(onYearMonthSelect);



				// Build year selector

				var yearSelect = $('<select/>')

									.hide()

									.change(onYearMonthSelect);



				// Build month label

				var monthText = $('<span/>')

									.html(monthNames[firstDateMonth])

									.mousedown(function() { return false; })

									.click(function(e) {

										e.stopPropagation();

										toggleYearMonthSelect(false);

									});



				// Build year label

				var yearText = $('<span/>')

									.html(firstDateYear)

									.mousedown(function() { return false; })

									.click(function(e) {

										e.stopPropagation();

										toggleYearMonthSelect(true);

									});



				// Populate month select

				$.each(monthNames, function(i, v) {

					if(options.allowMonthSelect && selectableMonths._indexOf(i) != -1) {

						var o = $('<option/>').html(v).attr('value', i);

						if(i == firstDateMonth) { o.attr('selected', 'selected');}

						monthSelect.append(o);

					}

				});



				// Populate year select

				$.each(selectableYears, function(i, v) {

					if(options.allowYearSelect) {

						var o = $('<option/>').html(v).attr('value', v);

						if(v == firstDateYear) { o.attr('selected', 'selected'); }

						yearSelect.append(o);

					}

				});



				var titleYearMonth = $('<div/>')

										.append(monthText)

										.append(monthSelect)

										.append(yearText)

										.append(yearSelect);



				// Add to title

				titleCell.children().remove();

				titleCell.append(titleYearMonth);



				// Run the callback signaling end of the render

				renderCalback = renderCalback || (function() {});

				renderCalback();

			}

		};



		// Return the plugin

		return glDatePicker;

	})();



	// One time initialization of useful prototypes

	(function() {

		Date.prototype._clear = function() {

			this.setHours(0);

			this.setMinutes(0);

			this.setSeconds(0);

			this.setMilliseconds(0);



			return this;

		};



		Date.prototype._time = function() {

			return this._clear().getTime();

		};



		Date.prototype._max = function() {

			var isLeapYear = (new Date(this.getYear(), 1, 29).getMonth() == 1) ? 1 : 0;

			var days = [31, 28 + isLeapYear, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];



			return days[this.getMonth()];

		};



		Date.prototype._add = function(days) {

			this.setDate(this.getDate() + days);

		};



		Date.prototype._first = function() {

			var date = new Date(this);

				date.setDate(1);



			return date;

		};



		Date.prototype._val = function() {

			this._clear();



			return {

				year: this.getFullYear(),

				month: this.getMonth(),

				date: this.getDate(),

				time: this.getTime(),

				day: this.getDay()

			};

		};



		Array.prototype._indexOf = function(value) {

			return $.inArray(value, this);

		}

	})();

})();

